// $Id: TestParallelAgent.java 1932 2013-01-29 14:40:05Z phil $

/*
 * typical call =
 * java jade.Boot -gui &
 * <br>
 * (on other machines, several ComputeAgent:)
 * java jade.Boot -container -host <server> ci:pl.jade.ComputeAgent
 * <br>
 * java jade.Boot -container test:pl.jade.TestParallelAgent
 *
 */

package sma.jade;

import jade.core.*;
import jade.core.behaviours.*;
import jade.domain.*;
import jade.domain.FIPAAgentManagement.*;
import jade.lang.acl.*;

/**
 * Test application for distributed computing sample.
 * The task (computing an integral of a given real function on a
 *   given interval, with a given precision) is first achieved
 *   directly in current thread, then indirectly using computing agents.
 * <br>
 * Steps: <ol>
 * <li> send message to know how many ComputeAgent (<em>N</em>)
 *      live in current platform.
 * <li> divide the integration interval in <em>N</em> sub-intervals, and
 *      ask each ComputeAGent to compute a partial sum
 * <li> sum all responses from ComputeAGents and print final result.
 * </ol>
 * The chosen example computes by default log(e) as sum(1,e) 1/x dx <br>
 * The values (start, end, delta) can be given in the command-line. <br>
 * In both case, CPU time is measured. When delta increases (finer
 *   computation grain) performance for distributed version becomes
 *   better than for single-thread version (ex 2 ComputeAgent on a dual core)
 *
 * @author laroque@u-cergy.fr
 * @version $Rev: 1932 $
 */
public class TestParallelAgent extends Agent {

  /** the final result (sum of COmputeAgents' results) */
  protected double total;

  /** time stamps to measure computations duration */
  protected long timestamp;

  /**
   * Retrieve the number of ComputeAgents, divide the computation to
   * distribute among the agents, send each agent a partial task,
   * then launch a listening cyclic behaviour to collect partial results
   */
  protected void setup() {

    // parameters for the computation: interval bounds and granularity
    // default value if nothing on the command-line
    double s = 1,
      e = Math.exp(1),
      d = 0.0000001;

    // extracting values from command line
    Object[] args = getArguments();
    if (args != null)
      try {
        s = Double.parseDouble( args[0].toString() );
        e = Double.parseDouble( args[1].toString() );
        d = Double.parseDouble( args[2].toString() );
      } catch (Exception ex){ }

    final double start = s;
    final double end = e;
    final double delta = d;

    System.err.println("** start = " + start
                       + ", end = "  + end
                       + ", delta = " + delta
                       );

    // start chrono for single-threaded computation
    timestamp = System.currentTimeMillis();

    // the function object to evaluate
    Function f = new MyFunction(start,end,delta);

    double single = f.eval();

    // stop chrono for single-threaded computation and display result
    System.err.println("** single : " + single + " in "
                       + (System.currentTimeMillis() - timestamp)
                       + " ms."
                       );

    // init distributed computation
    total = 0;

    // start chrono for distributed computation
    timestamp = System.currentTimeMillis();

    // retrieve ComputeAgents, build message and send it
    addBehaviour(new OneShotBehaviour(this) {
        public void action() {
          // looking for "compute" service
          ServiceDescription sd  = new ServiceDescription();
          sd.setType( "compute" );
          DFAgentDescription dfd = new DFAgentDescription();
          dfd.addServices(sd);
          try {
            // bring back the list of providers
            DFAgentDescription[] result = DFService.search(myAgent, dfd);

            // launch a cyclic, listening behaviour to collect results
            myAgent.addBehaviour(new CollectBehaviour(myAgent, result.length));

            // build and send request to every provider
            // (each with a different interval)
            double origin = start;
            double size = (end-start) / result.length;
            for (int i = 0; i < result.length; i++) {
              // for each ComputeAGent,
              ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
              // build a message to make it compute its part of the job
              Function f = new MyFunction(origin,origin+size,delta);
              origin += size;
              msg.addReceiver(result[i].getName());
              System.err.println(result[i].getName().getLocalName()
                                 + " can compute ");
              msg.setContentObject(f);
              // and send the message
              send(msg);
            }
          }
          catch (FIPAException fe) { fe.printStackTrace(); }
          catch (java.io.IOException ioe) { ioe.printStackTrace(); }
        }
      });
  }

  /** Cyclicly listen to partial results coming from CompluteAgents.
   * When all partial results are returned, display their sum and exit
   */
  class CollectBehaviour extends SimpleBehaviour {
    /** # of results received until now */
    protected int contribs;

    /** # of results we are waiting for */
    protected final int expectedContribs;

    /** the constructor tells how many contribs are expected
     * @param a the agent this behaviour is attached to
     * @param contribs the number of expected partial results
     */
    public CollectBehaviour(Agent a, int contribs) {
      super(a);
      this.contribs = 0;
      expectedContribs = contribs;
      System.err.println("collect expects " + contribs + " contribs");
    }

    /** (cyclic) check for a partial result.
     * If any, increment sum. If all contribs are known,
     *    display total sum and computing time
     */
    public void action() {
      // check for new contrib
      ACLMessage msg= receive();
      if (msg!=null) {

        String res = msg.getContent().toString();
        System.err.println("***** res == " + res);
        System.err.println("*****");

        double d = 0;
        
        try {
        // collect this contribution
          System.err.println("***** " + msg.getContent().toString());
        d = Double.parseDouble(msg.getContent().toString());
        System.err.println("gather received : " + d);
        } catch (Exception e) {e.printStackTrace();}
        // update sum and # of received contribs
        total += d;
        contribs++;
      }

      else block();
    }

    /** Behaviour stops when all expected con tribs have returned
     *   a result. A message indicating the final result as well as the
     *   computation time is finally displayed.
     * @return true iff all expected contribs have returned their result.
     */
    public boolean done() {
      if (contribs == expectedContribs) {
        System.err.println("FINAL RESULT = "
                           + total
                           + " in "
                           + (System.currentTimeMillis() - timestamp)
                           + " ms."
                           );

        return true;
      }
      return false;
    }
  }
}
